/*
 * Copyright (C) 2012  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.util.osgi;

import java.util.ArrayList;
import java.util.List;

import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;

/**
 * FilterBuilder : The OSGi filter builder class.
 * 
 * @author Romain DUBOIS
 */
public class FilterBuilder {

    /**
     * Get a simple filter.
     * 
     * @param pProperty
     *            The filtered property
     * @param pValue
     *            The required value
     * @return The filter
     */
    public static String getStringFilter(final String pProperty, final String pValue) {
        return getStringFilter(pProperty, pValue, false);
    }

    /**
     * Get a simple filter.
     * 
     * @param pProperty
     *            The filtered property
     * @param pValue
     *            The required value
     * @param pNegate
     *            If the value is required or forbidden
     * @return The filter
     */
    public static String getStringFilter(final String pProperty, final String pValue,
            final boolean pNegate) {
        if (pNegate) {
            return new FilterBuilder().and(pProperty, pValue).negate().toStringFilter();
        }
        return new FilterBuilder().and(pProperty, pValue).toStringFilter();
    }

    /**
     * Get a simple filter.
     * 
     * @param pProperty
     *            The filtered property
     * @param pValue
     *            The required value
     * @return The filter
     * @throws InvalidSyntaxException
     *             If filter contains an invalid filter string that cannot be
     *             parsed.
     */
    public static Filter getFilter(final String pProperty, final String pValue)
            throws InvalidSyntaxException {
        return getFilter(pProperty, pValue, false);
    }

    /**
     * Get a simple filter.
     * 
     * @param pProperty
     *            The filtered property
     * @param pValue
     *            The required value
     * @param pNegate
     *            If the value is required or forbidden
     * @return The filter
     * @throws InvalidSyntaxException
     *             If filter contains an invalid filter string that cannot be
     *             parsed.
     */
    public static Filter getFilter(final String pProperty, final String pValue,
            final boolean pNegate) throws InvalidSyntaxException {
        if (pNegate) {
            return new FilterBuilder().and(pProperty, pValue).negate().toFilter();
        }
        return new FilterBuilder().and(pProperty, pValue).toFilter();
    }

    /** The negation start string */
    private static final String NEGATE_START = "(!"; //$NON-NLS-1$
    /** The negation end string */
    private static final String NEGATE_END = ")"; //$NON-NLS-1$

    /** The link operator */
    private boolean mAnd;
    /** The sub filters */
    private final List<String> mContent;

    /**
     * Constructor.
     */
    public FilterBuilder() {
        super();

        mContent = new ArrayList<String>();
    }

    /**
     * Constructor
     * 
     * @param pInitContent
     *            The initial content
     */
    public FilterBuilder(final String pInitContent) {
        this();

        mContent.add(pInitContent);
    }

    /**
     * Append an additional filter.
     * 
     * @param pBuilder
     *            The filter to merge
     * @return The current filter
     */
    public FilterBuilder and(final FilterBuilder pBuilder) {
        merge(pBuilder, true);
        return this;
    }

    /**
     * Append a substitution filter.
     * 
     * @param pBuilder
     *            The filter to merge
     * @return The current filter
     */
    public FilterBuilder or(final FilterBuilder pBuilder) {
        merge(pBuilder, false);
        return this;
    }

    /**
     * Append an additional filter.
     * 
     * @param pProperty
     *            The filtered property
     * @param pValue
     *            The required value
     * @return The current filter
     */
    public FilterBuilder and(final String pProperty, final String pValue) {
        append(build(pProperty, pValue, false), true);
        return this;
    }

    /**
     * Append a substitution filter.
     * 
     * @param pProperty
     *            The filtered property
     * @param pValue
     *            The required value
     * @return The current filter
     */
    public FilterBuilder or(final String pProperty, final String pValue) {
        append(build(pProperty, pValue, false), false);
        return this;
    }

    /**
     * Append an additional filter.
     * 
     * @param pProperty
     *            The filtered property
     * @param pValue
     *            The forbidden value
     * @return The current filter
     */
    public FilterBuilder andNot(final String pProperty, final String pValue) {
        append(build(pProperty, pValue, true), true);
        return this;
    }

    /**
     * Append a substitution filter.
     * 
     * @param pProperty
     *            The filtered property
     * @param pValue
     *            The forbidden value
     * @return The current filter
     */
    public FilterBuilder orNot(final String pProperty, final String pValue) {
        append(build(pProperty, pValue, true), false);
        return this;
    }

    /**
     * Negate the current filter.
     * 
     * @return The current filte
     */
    public FilterBuilder negate() {
        refactor(true);
        return this;
    }

    /**
     * Get the filter of the current content.
     * 
     * @return The current filter
     */
    public String toStringFilter() {
        switch (mContent.size()) {
            case 0:
                return ""; //$NON-NLS-1$

            case 1:
                return mContent.get(0);

            default:
                final StringBuilder lBuilder = new StringBuilder();
                lBuilder.append('(');
                if (mAnd) {
                    lBuilder.append('&');
                } else {
                    lBuilder.append('|');
                }
                for (final String lFilter : mContent) {
                    lBuilder.append(lFilter);
                }
                lBuilder.append(')');
                return lBuilder.toString();
        }
    }

    /**
     * Get the filter of the current content.
     * 
     * @return The current filter
     * @throws InvalidSyntaxException
     *             If filter contains an invalid filter string that cannot be
     *             parsed.
     */
    public Filter toFilter() throws InvalidSyntaxException {
        return FrameworkUtil.createFilter(toStringFilter());
    }

    /**
     * Build a single filter.
     * 
     * @param pProperty
     *            The filtered property
     * @param pValue
     *            The filtered value
     * @param pNegate
     *            If the value is required or forbidden
     * @return The string filter
     */
    private String build(final String pProperty, final String pValue, final boolean pNegate) {
        final StringBuilder lBuilder = new StringBuilder();
        lBuilder.append('(');
        if (pNegate) {
            lBuilder.append(NEGATE_START);
        }
        lBuilder.append(pProperty).append('=').append(pValue);
        if (pNegate) {
            lBuilder.append(NEGATE_END);
        }
        lBuilder.append(')');

        return lBuilder.toString();
    }

    /**
     * Append a sub filter to the current content.
     * 
     * @param pFilter
     *            The filter to add
     * @param pAnd
     *            The link operator to use
     */
    private void append(final String pFilter, final boolean pAnd) {
        ensureCompatible(pAnd);
        mAnd = pAnd;
        mContent.add(pFilter);
    }

    /**
     * Ensure the filter is compatible with the link operator.
     * 
     * @param pAnd
     *            The lonk operator
     */
    private void ensureCompatible(final boolean pAnd) {
        if (mContent.size() > 1 && mAnd != pAnd) {
            refactor(false);
        }
    }

    /**
     * Refactor the current builder.
     * 
     * @param pNegate
     *            If the current content has to be negated
     */
    private void refactor(final boolean pNegate) {
        String lCurrent = toStringFilter();
        mContent.clear();
        if (pNegate) {
            lCurrent = NEGATE_START + lCurrent + NEGATE_END;
        }
        mContent.add(lCurrent);
    }

    /**
     * Merge two filter builders
     * 
     * @param pBuilder
     *            The other builder
     * @param pAnd
     *            The link operator to use
     */
    private void merge(final FilterBuilder pBuilder, final boolean pAnd) {
        // Ensure other is compatible
        pBuilder.ensureCompatible(pAnd);

        // Ensure this is compatible
        ensureCompatible(pAnd);

        // Merge
        mContent.addAll(pBuilder.mContent);
        mAnd = pAnd;
    }
}
